package com.roc.admin.infrastructure.common.aspect;

import cn.dev33.satoken.stp.StpUtil;
import com.roc.admin.domain.entity.OperationLog;
import com.roc.admin.domain.repository.OperationLogRepository;
import com.roc.admin.interfaces.common.annotation.Log;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Arrays;

/**
 * 操作日志切面
 */
@Aspect
@Component
public class OperationLogAspect {

    private final OperationLogRepository operationLogRepository;
    private final ObjectMapper objectMapper;

    public OperationLogAspect(OperationLogRepository operationLogRepository, ObjectMapper objectMapper) {
        this.operationLogRepository = operationLogRepository;
        this.objectMapper = objectMapper;
    }

    /**
     * 配置切入点
     */
    @Pointcut("@annotation(com.roc.admin.interfaces.common.annotation.Log)")
    public void logPointcut() {
    }

    /**
     * 处理完请求后执行
     *
     * @param joinPoint 切点
     * @param result    返回值
     */
    @AfterReturning(pointcut = "logPointcut()", returning = "result")
    public void doAfterReturning(JoinPoint joinPoint, Object result) {
        handleLog(joinPoint, null, result);
    }

    /**
     * 拦截异常操作
     *
     * @param joinPoint 切点
     * @param e         异常
     */
    @AfterThrowing(value = "logPointcut()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
        handleLog(joinPoint, e, null);
    }

    /**
     * 处理日志
     *
     * @param joinPoint 切点
     * @param e         异常
     * @param result    返回值
     */
    private void handleLog(JoinPoint joinPoint, Exception e, Object result) {
        try {
            // 获取注解
            Log logAnnotation = getLogAnnotation(joinPoint);
            if (logAnnotation == null) {
                return;
            }

            // 获取当前请求
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (attributes == null) {
                return;
            }

            HttpServletRequest request = attributes.getRequest();

            // 创建操作日志
            OperationLog operationLog = new OperationLog();
            operationLog.setModule(logAnnotation.module());
            operationLog.setType(logAnnotation.type());
            operationLog.setDescription(logAnnotation.description());
            operationLog.setMethod(request.getMethod());
            operationLog.setUrl(request.getRequestURI());
            operationLog.setIp(getIpAddress(request));
            operationLog.setParams(getParams(joinPoint));
            operationLog.setOperateTime(LocalDateTime.now());

            // 设置操作人信息
            if (StpUtil.isLogin()) {
                operationLog.setOperateUserId(StpUtil.getLoginIdAsLong());
                operationLog.setOperateUsername(StpUtil.getLoginIdAsString());
            }

            // 设置操作结果
            if (e != null) {
                operationLog.setStatus(0);
                operationLog.setErrorMsg(e.getMessage());
            } else {
                operationLog.setStatus(1);
                operationLog.setResult(objectMapper.writeValueAsString(result));
            }

            // 保存操作日志
            operationLogRepository.save(operationLog);
        } catch (Exception ex) {
            // 记录日志失败，不影响业务
            ex.printStackTrace();
        }
    }

    /**
     * 获取日志注解
     *
     * @param joinPoint 切点
     * @return 日志注解
     */
    private Log getLogAnnotation(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        return method.getAnnotation(Log.class);
    }

    /**
     * 获取请求参数
     *
     * @param joinPoint 切点
     * @return 请求参数
     */
    private String getParams(JoinPoint joinPoint) {
        try {
            String[] paramNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
            Object[] args = joinPoint.getArgs();
            if (paramNames.length == 0 || args.length == 0) {
                return "";
            }
            StringBuilder params = new StringBuilder();
            for (int i = 0; i < paramNames.length; i++) {
                params.append(paramNames[i]).append(": ").append(objectMapper.writeValueAsString(args[i])).append(", ");
            }
            return params.substring(0, params.length() - 2);
        } catch (Exception e) {
            return Arrays.toString(joinPoint.getArgs());
        }
    }

    /**
     * 获取IP地址
     *
     * @param request 请求
     * @return IP地址
     */
    private String getIpAddress(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }
}
